﻿using System.Collections.Generic;
using UnityEngine;
namespace GX
{

    public class TickManagerService : ITickManager
    {
        class Ticker : ITickable
        {
            int _id;

            int _delay;
            int _interval;
            int _totalCount;
            bool _isRealtime;

            int _repeatedCount;
            int _totalElapsedTime;
            int _elapsedTime;
            bool _isStarted = false;

            public TickDelegate callback;
            public int id { get { return _id; } }
            public bool isRealtime { get { return _isRealtime; } }
            public bool isFrame { get { return false; } }
            public int startDelay { get { return _delay; } }
            public int interval { get { return _interval; } }
            public int totalCount { get { return _totalCount; } }
            public int repeatedCount { get { return _repeatedCount; } }
            public bool isStarted { get { return _isStarted; } }
            public int elapsedTime { get { return _elapsedTime; } }
            public int totalElapsedTime { get { return _totalElapsedTime; } }
            public bool isDone { get { return _totalCount != 0 && _repeatedCount >= _totalCount; } }
            public void Start(int id, int interval, int repeatCount, int startDelay, bool isRealtime)
            {
                _id = id;
                _interval = interval < 1 ? 1 : interval;
                _totalCount = repeatCount < 0 ? 0 : repeatCount;
                _delay = startDelay < 0 ? 0 : startDelay;
                _isRealtime = isRealtime;

                _repeatedCount = 0;
                _totalElapsedTime = 0;
                _elapsedTime = 0;
                _isStarted = _delay == 0;
            }

            public bool Update(int deltaTime)
            {
                _totalElapsedTime += deltaTime;
                if (!_isStarted)
                {
                    if (_totalElapsedTime < _delay)
                        return false;
                    else
                    {
                        _elapsedTime = _totalElapsedTime - _delay;
                        _isStarted = true;
                    }
                }
                else
                    _elapsedTime += deltaTime;

                if (_elapsedTime < _interval)
                    return false;

                _elapsedTime -= _interval;
                ++_repeatedCount;
                return true;
            }
        }

        const int MsPerSecond = 1000;

        readonly List<Ticker> _tickers = new List<Ticker>(64);
        int _cachedGUID;
        bool _isTraverse;
        int _firstEmptyIndex = -1;

        int _time;
        int _realTime;
        int _deltaTime;
        int _deltaRealtime;
        int _fameCount;
        public int time { get { return _time; } }
        public int realTime { get { return _realTime; } }
        public int deltaTime { get { return _deltaTime; } }
        public int deltaRealTime { get { return _deltaRealtime; } }
        public int frameCount { get { return _fameCount; } }
        bool TryGetIndex(int id, out int index)
        {
            int len = _tickers.Count;
            int low = 0, high = len - 1;
            int pos = -1;
            int key;
            bool bigger = true;
            Ticker ticker;
            while (true)
            {
                if (low > high)
                    break;
                pos = (low + high) / 2;
                ticker = _tickers[pos];
                key = ticker.id;
                if (key == id)
                {
                    index = pos;
                    return true;
                }
                if (id > key)
                {
                    bigger = true;
                    low = pos + 1;
                }
                else
                {
                    bigger = false;
                    high = pos - 1;
                }
            }

            if (bigger)
                index = pos + 1;
            else
                index = pos;
            return false;
        }
        public int Tick(int interval, int repeatCount, TickDelegate callback, int startDelay, bool isRealtime)
        {
            int index = 0;
            int len = _tickers.Count;
            Ticker ticker;
            for (int i = 0; i < len; ++i)
            {
                ticker = _tickers[i];
                if (ticker.callback == callback)
                {
                    return ticker.id;
                }
            }
            while (true)
            {
                ++_cachedGUID;
                if (_cachedGUID != 0 && !TryGetIndex(_cachedGUID, out index))
                    break;
            }
            
            ticker = PoolManager.Get<Ticker>();
            _tickers.Insert(index, ticker);
            ticker.callback = callback;
            ticker.Start(_cachedGUID, interval, repeatCount, startDelay, isRealtime);
            return _cachedGUID;
        }
        public bool Cancel(int tickID)
        {
            int index;
            if (!TryGetIndex(tickID, out index))
                return false;
            _tickers[index].callback = null;
            if (_isTraverse)
            {
                if (_firstEmptyIndex == -1 || index < _firstEmptyIndex)
                    _firstEmptyIndex = index;
            }
            else
            {
                var ticker = _tickers[index];
                PoolManager.Release( ticker);
                _tickers.RemoveAt(index);
            }
            return true;
        }
        public void Update(int frameCount, float time, float realTime)
        {
            _fameCount = frameCount;// Time.frameCount;

            int value = (int)(time * MsPerSecond); //(int)(Time.time * MsPerSecond);
            _deltaTime = value - _time;
            _time = value;

            value = (int)(realTime * MsPerSecond); //(int)(Time.realtimeSinceStartup * MsPerSecond);
            _deltaRealtime = value - _realTime;
            _realTime = value;

            if (_isTraverse)
                return;
            _isTraverse = true;

            int len = _tickers.Count;
            Ticker ticker;
            TickDelegate del;
            for (int i = 0; i < len; ++i)
            {
                ticker = _tickers[i];
                if (null == ticker.callback)
                    continue;
                if (ticker.Update(ticker.isRealtime ? _deltaRealtime : _deltaTime))
                {
                    del = ticker.callback;
                    if (ticker.isDone)
                    {
                        ticker.callback = null;
                        if (_firstEmptyIndex == -1 || i < _firstEmptyIndex)
                            _firstEmptyIndex = i;
                    }
                    del(ticker);
                }
            }

            _isTraverse = false;

            if (_firstEmptyIndex != -1)
            {
                PoolManager.Release( _tickers[_firstEmptyIndex]);
                int pos = _firstEmptyIndex;
                len = _tickers.Count;
                for (int i = _firstEmptyIndex + 1; i < len; ++i)
                {
                    ticker = _tickers[i];
                    if (null != ticker.callback)
                    {
                        _tickers[pos++] = ticker;
                    }
                    else
                    {
                        PoolManager.Release( ticker);
                    }
                }
                _tickers.RemoveRange(pos, len - pos);
                _firstEmptyIndex = -1;
            }
        }
    }
}